package com.pagoda.viewfinder_compiler;

import com.google.auto.service.AutoService;
import com.pagoda.viewfinder_annotation.BindClick;
import com.pagoda.viewfinder_annotation.BindView;
import com.pagoda.viewfinder_compiler.model.BindClickMethod;
import com.pagoda.viewfinder_compiler.model.BindViewField;
import com.pagoda.viewfinder_compiler.model.ViewFinderClass;

import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

/**
 * E-mail: liaohailong190@foxmail.com
 * Date: 2020/7/16 16:25
 * Description:
 *  apt demo总结：
 *  1，compiler和annotation模块一定要是javaLibrary
 *  2，compiler使用AutoService的话，需要implementation+annotationProcessor，否则找不到包
 *  3，app模块如果是java工程则使用annotationProcessor 'compiler模块' 如果是kotlin，则使用kapt插件
 *  4，熟读RoundEnvironment、Element、还有JavaPoet框架，用来生成新代码。
 *  5，其实annotation模块和compiler模块最后可以依赖到一个core模块里面，专供外部调用。
 * @author liaohailong
 */
@AutoService(Processor.class)
public class ViewFinderProcessor extends AbstractProcessor {

    private Filer mFiler;
    private Elements mElements;
    private Messager mMessager;

    private final Map<String, ViewFinderClass> cacheMap = new HashMap<>();

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        mFiler = processingEnvironment.getFiler();
        mElements = processingEnvironment.getElementUtils();
        mMessager = processingEnvironment.getMessager();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new LinkedHashSet<>();
        types.add(BindView.class.getCanonicalName());
        types.add(BindClick.class.getCanonicalName());
        return types;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.RELEASE_8;
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        cacheMap.clear();

        try {
            processBindView(roundEnvironment);
            processBindClick(roundEnvironment);
        } catch (Exception e) {
            error(e.getMessage());
            return true;
        }

        for (ViewFinderClass clz : cacheMap.values()) {
            try {
                info("Generating file for %s", clz.getFullClassName());
                clz.generateFinder().writeTo(mFiler);
            } catch (Exception e) {
                error("Generate file failed, reason: %s", e.getMessage());
                return true;
            }
        }


        return true; // stop process
    }

    private ViewFinderClass getClass(String className, TypeElement typeElement) {
        ViewFinderClass viewFinderClass = cacheMap.get(className);
        if (viewFinderClass == null) {
            viewFinderClass = new ViewFinderClass(typeElement, mElements);
            cacheMap.put(className, viewFinderClass);
        }
        return viewFinderClass;
    }

    private void processBindView(RoundEnvironment roundEnvironment) {

        for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
            // get class for field
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            // class path
            String className = typeElement.getQualifiedName().toString();

            ViewFinderClass viewFinderClass = getClass(className, typeElement);

            BindViewField bindViewField = new BindViewField(element);
            viewFinderClass.addField(bindViewField);
        }
    }

    private void processBindClick(RoundEnvironment roundEnvironment) {

        for (Element element : roundEnvironment.getElementsAnnotatedWith(BindClick.class)) {
            // get class for field
            TypeElement typeElement = (TypeElement) element.getEnclosingElement();
            // class path
            String className = typeElement.getQualifiedName().toString();

            ViewFinderClass viewFinderClass = getClass(className, typeElement);

            BindClickMethod bindClickMethod = new BindClickMethod(element);
            viewFinderClass.addMethod(bindClickMethod);
        }
    }


    private void error(String msg, Object... args) {
        if (mMessager != null) {
            mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args));
        }
    }

    private void info(String msg, Object... args) {
        if (mMessager != null) {
            mMessager.printMessage(Diagnostic.Kind.NOTE, String.format(msg, args));
        }
    }

}
